import re
import yaml
from urlparse import urlparse

from google.appengine.ext import db
from google.appengine.api import users

class Link(db.Model):
	url = db.StringProperty()
	description = db.TextProperty()
	owner = db.UserProperty()
	domain = db.StringProperty()
	superdomains = db.StringListProperty()
	
	# metainfo
	retrieved = db.DateTimeProperty()
	final_url = db.StringProperty() # after redirects
	favicon = db.BlobProperty()	# sequence of bytes
	title = db.StringProperty()
	meta_description = db.TextProperty()
	meta_keywords = db.StringListProperty()
#	linked_pages
#	preview

	tags = db.StringListProperty()
	link_data = db.StringProperty(multiline=True)
	attr_data = db.StringProperty(multiline=True)

	created = db.DateTimeProperty(auto_now_add=True)
	modified = db.DateTimeProperty(auto_now=True)
	private = db.BooleanProperty(default=False)

	def __init__(self, *args, **kwargs):
		tags = kwargs.get('tags', [])
		if isinstance(tags, basestring):
			if tags == "":
				tags = []
			else:
				tags = tags.split(',')
				tags = [tag.strip() for tag in tags]
				tags = filter(None, tags)
			kwargs['tags'] = tags

		super(Link, self).__init__(*args, **kwargs)

	def put(self):
		self.domain, self.superdomains = self._extract_domain()
		return super(Link, self).put()

	def _extract_domain(self):
		parse_result = urlparse(self.url)
		domain = parse_result.netloc

		port = parse_result.port
		if port is not None:
			# remove trailing port designation ":XXXX"
			domain = domain.rsplit(':', 1)[-2]

		superdomains = list()
		superdomain = ''
		for word in domain.split('.')[::-1]:
			# ['com', 'example', 'www']
			if superdomain == '':
				superdomain = word
			else:
				superdomain = word + '.' + superdomain
			# ['com', 'example.com', 'www.example.com']
			superdomains.append(superdomain)

		superdomains.reverse()
		return domain, superdomains


	def attributes_as_dict(self):
		if self.attr_data:
			return yaml.load(self.attr_data)
		else:
			return {}


	def user_can_edit(self, user):
		"""Return true if the given user has permission to edit this link."""
		return user and (user == self.owner or self.is_collaborator(user)
										 or users.is_current_user_admin())

	@property
	def edit_allowed(self):
		"""Whether the current user can edit this link."""
		account = Account.current_user_account
		if account is None:
			return False
		return self.user_can_edit(account.user)

	@staticmethod
	def _collaborator_emails_from_description(description):
		"""Parses a description, returning collaborator email addresses.

		Broken out for unit testing.
		"""
		collaborators = []
		for line in description.splitlines():
			m = re.match(
				r'\s*COLLABORATOR\s*='
				r'\s*([a-zA-Z0-9._]+@[a-zA-Z0-9_]+\.[a-zA-Z0-9._]+)\s*',
				line)
			if m:
				collaborators.append(m.group(1))
		return collaborators

	def collaborator_emails(self):
		"""Returns a possibly empty list of emails specified in
		COLLABORATOR= lines.

		Note that one COLLABORATOR= lines is required per address.
		"""
		if not self.description:
			return []
		return Link._collaborator_emails_from_description(self.description)

	def is_collaborator(self, user):
		"""Returns true if the given user is a collaborator on this link.

		This is determined by checking if the user's email is listed as a
		collaborator email.
		"""
		if not user:
			return False
		return user.email() in self.collaborator_emails()


class Account(db.Model):
	user = db.UserProperty()
	email = db.EmailProperty()
	nickname = db.StringProperty(required=True)
	created = db.DateTimeProperty(auto_now_add=True)
	modified = db.DateTimeProperty(auto_now=True)

	# Current user's Account. Updated by middleware.AddUserToRequestMiddleware.
	current_user_account = None

	lower_email = db.StringProperty()
	lower_nickname = db.StringProperty()

	def put(self):
		self.lower_email = str(self.email).lower()
		self.lower_nickname = self.nickname.lower()
		return super(Account, self).put()

	@classmethod
	def get_account_for_user(cls, user):
		"""Get the Account for a user, creating a default one if needed."""
		email = user.email()
		assert email
		key = '<%s>' % email
		# Since usually the account already exists, first try getting it
		# without the transaction implied by get_or_insert().
		account = cls.get_by_key_name(key)
		if account is not None:
			return account
		nickname = cls.create_nickname_for_user(user)
		return cls.get_or_insert(
				key, user=user, email=email, nickname=nickname, fresh=True)

	@classmethod
	def create_nickname_for_user(cls, user):
		"""Returns a unique nickname for a user."""
		name = nickname = user.email().split('@', 1)[0]
		next_char = chr(ord(nickname[0].lower())+1)
		existing_nicks = [
				account.lower_nickname for account in cls.gql((
				'WHERE lower_nickname >= :1 AND lower_nickname < :2'),
				nickname.lower(), next_char)]
		suffix = 0
		while nickname.lower() in existing_nicks:
			suffix += 1
			nickname = '%s%d' % (name, suffix)
		return nickname

	@classmethod
	def get_account_for_nickname(cls, nickname):
		"""Get the list of Accounts that have this nickname."""
		assert nickname
		assert '@' not in nickname
		return cls.all().filter('lower_nickname =', nickname.lower()).get()
